/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.factory.server.service;

import com.factory.server.analyser.Analyser;
import com.factory.server.bo.Prime;
import com.factory.server.bo.User;
import com.factory.server.bo.UserTask;
import com.factory.server.cache.InputCache;
import com.factory.server.cache.OutputCache;
import com.factory.server.consumers.ResultQueue;
import com.factory.server.consumers.ResultSaver;
import com.factory.server.dao.DAODispatcherIface;
import com.factory.server.dto.UserDTO;
import com.factory.server.dto.UserTaskDTO;
import com.factory.server.dto.UserTasksWrapper;
import com.factory.server.dto.UsersWrapper;
import com.factory.server.helper.InputWrapper;
import com.factory.server.json.AlgorithmicProblem;
import com.factory.server.json.Plan;
import com.factory.server.json.SchedulerResult;
import com.factory.server.json.Stats;
import com.factory.server.provider.HashProvider;
import com.factory.server.scheduler.SchedulerServiceIface;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

/**
 * Provides implementation for web services published in FactoryREST and other
 * supporting functions.
 *
 * @author milos
 */
@Service
public class FactoryServiceImpl implements FactoryServiceIface, ApplicationContextAware {

    @Autowired
    @Qualifier("daoDispatcher")
    private DAODispatcherIface genericDAODispacher;
    @Autowired
    private SchedulerServiceIface schedulerService;
    @Autowired
    private Analyser analyser;
    @Autowired
    private DataServiceIface dataService;
    private ApplicationContext applicationContext = null;
    private InputSaver storeInputThread = null;
    private ResultSaver storeResultThread = null;
    @Autowired
    private transient HashProvider hashProvider;
    static final Logger LOG = Logger.getLogger(FactoryServiceImpl.class.getName());

    public String fileUpload(String userId, AlgorithmicProblem problem, List<String> linesList, List<String> parameters) {

        //inputId is generated from userId and currenttime
        String inputId = DigestUtils.md5Hex(String.valueOf(System.currentTimeMillis()) + userId).toString();

        dataService.initialize(userId, inputId, linesList, problem.name(), parameters);

        //storing will run in own thread and cache will be used
        InputWrapper inputWrapper = new InputWrapper(inputId, dataService.transformToBOList());

        InputCache.add(inputId, inputWrapper);


        //if not running run store thread for input and store thread for result
        ThreadPoolTaskExecutor taskExecutor = (ThreadPoolTaskExecutor) applicationContext.getBean("taskExecutor");


        //pri prvom spusteni
        if (storeInputThread == null) {
            storeInputThread = (InputSaver) applicationContext.getBean("inputSaver");
            taskExecutor.execute(storeInputThread);

            schedulerService.setInputQueue(new InputQueue());
            ResultQueue resultQueue = new ResultQueue();
            resultQueue.setApplicationContext(applicationContext);
            schedulerService.setResultQueue(resultQueue);
        }

        if (storeResultThread == null) {
            storeResultThread = (ResultSaver) applicationContext.getBean("resultSaver");
        }

        taskExecutor.execute(storeResultThread);
        schedulerService.getInputQueue().put(inputWrapper);

        //pri prvom pusteni nacitam do cache vsetky prvocisla z DB
        if (OutputCache.primes == null || OutputCache.primes.isEmpty()) {
            OutputCache.primes = new ArrayList<Prime>();
            List<Prime> dbPrimes = genericDAODispacher.getAll(Prime.class);
            OutputCache.primes.addAll(dbPrimes);
        }

        addUserTask(userId, problem.name(), inputId, parameters, dataService.getNodesCount());
        return inputId;
    }

    public boolean register(UserDTO userDTO) {
        User u = new User(userDTO);
        List<User> blockedUsers = genericDAODispacher.getByProperty("blocked", true, User.class);
        for (User user : blockedUsers) {
            if (user.getEmail().equals(userDTO.getEmail())) {
                return false;
            }
        }
        genericDAODispacher.saveOrUpdate(u);
        return true;
    }

    public void addUserTask(String userId, String problemId, String inputId, List<String> parameters, Integer numberOfNodes) {
        UserTask userTask = new UserTask(inputId, problemId, userId, false);
        if (parameters != null && parameters.size() > 0) {
            userTask.setNote(parameters.get(0));
        } else { //pri topological order sa note vyuziva pre pocet uzlov
            if (numberOfNodes != null) {
                userTask.setNote(String.valueOf(numberOfNodes));
            }
        }
        genericDAODispacher.saveOrUpdate(userTask);
    }

    public SchedulerResult getSchedulerResult(String inputId) {
        return schedulerService.getSchedulerResult(inputId);
    }

    public void registerPlan(Plan plan) {
        schedulerService.registerPlan(plan);
    }

    public ByteArrayOutputStream getResultFile(String inputId) {

        List<?> resultList = dataService.getResult(inputId);
        if (resultList.isEmpty()) {
            return null;
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        for (int i = 0; i < resultList.size(); i++) {

            try {
                if (resultList.get(i) instanceof Prime) {
                    bos.write(((Prime) resultList.get(i)).toString().getBytes());
                } else {
                    bos.write(((String) resultList.get(i)).getBytes());
                }

                bos.write(Byte.decode("0xA"));
            } catch (IOException ex) {
                LOG.warning("Creation of result file failed. Exception: " + ex.getMessage());
            }
        }
        return bos;
    }

    public UserTasksWrapper getUserTasks(String userId) {
        List<UserTask> userTasksList = genericDAODispacher.getByProperty("userId", userId, UserTask.class);
        List<UserTaskDTO> userTasksDTOList = new ArrayList<UserTaskDTO>();
        for (UserTask task : userTasksList) {
            userTasksDTOList.add(new UserTaskDTO(task.getProblemId(), task.getUserId(), task.getInputId(), task.getDone()));
        }
        UserTasksWrapper userTasksWrapper = new UserTasksWrapper(userTasksDTOList);
        return userTasksWrapper;
    }

    public UsersWrapper getAllUsers() {
        UsersWrapper usersWrapper = new UsersWrapper();
        List<UserDTO> usersDTOList = new ArrayList<UserDTO>();
        List<User> usersList = genericDAODispacher.getAll(User.class);

        for (User user : usersList) {
            usersDTOList.add(new UserDTO(user.getLogin(), user.getEmail(), user.getPassword(), user.isAdmin(), user.isBlocked()));
        }

        usersWrapper.setUsersList(usersDTOList);
        return usersWrapper;
    }

    public void updateUser(UserDTO userDTO) {
        User user = genericDAODispacher.getByProperty("login", userDTO.getLogin(), User.class).get(0);
        user.setBlocked(userDTO.isBlocked());
        genericDAODispacher.saveOrUpdate(user);
    }

    public String authorize(UserDTO userDTO) {
        List<User> loginUsers;
        loginUsers = genericDAODispacher.getByProperty("login", userDTO.getLogin(), com.factory.server.bo.User.class);

        for (User u : loginUsers) {
            String salt = u.getSalt();
            String passDBHash = u.getPassword();
            if (passDBHash.equals(hashProvider.computeHash(userDTO.getPassHash() + salt))) {
                if (u.isBlocked()) {
                    return "NON_AUTHORIZED";
                } else {
                    if (u.isAdmin()) {
                        return "ADMIN";
                    } else {
                        return "USER";
                    }
                }
            }
        }

        return "NON_AUTHORIZED";
    }

    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public Stats getStats() {
        Stats stats = new Stats();
        Map<String, Integer> resolvedProblemCountMap = new HashMap<String, Integer>();
        Map<String, Integer> scheduledProblemCountMap = new HashMap<String, Integer>();


        List<UserTask> userTasks = genericDAODispacher.getAll(UserTask.class);

        for (UserTask userTask : userTasks) {
            String problemId = userTask.getProblemId();

            if (!resolvedProblemCountMap.containsKey(userTask.getProblemId())) {
                resolvedProblemCountMap.put(problemId, new Integer(0));
            }
            if (!scheduledProblemCountMap.containsKey(userTask.getProblemId())) {
                scheduledProblemCountMap.put(problemId, new Integer(0));
            }
            if (userTask.getDone() == Boolean.TRUE) {
                resolvedProblemCountMap.put(problemId, resolvedProblemCountMap.get(problemId) + 1);
            }
            scheduledProblemCountMap.put(problemId, scheduledProblemCountMap.get(problemId) + 1);
        }

        stats.setResolvedProblemCountMap(resolvedProblemCountMap);

        stats.setScheduledProblemCountMap(scheduledProblemCountMap);
        return stats;
    }

    public String pay(String userId) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public String paymentConfirmation(String userId) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
